'use strict';
var Cesium = require('cesium');
var fsExtra = require('fs-extra');
var path = require('path');

var defaultValue = Cesium.defaultValue;
var defined = Cesium.defined;
var Cartesian3 = Cesium.Cartesian3;
var Matrix4 = Cesium.Matrix4;

module.exports = combineTileset;

/**
 * Combie tileset into one tileset json.
 * 
 * @param {Object} options Object with following properties.
 * @param {String} options.inputDir Input directory include tilesets.
 * @param {String} [options.outputTileset="tileset.json"] Output tileset file path.
 */
function combineTileset(options) {
    var isBox = false;
    var west = Number.POSITIVE_INFINITY;
    var south = Number.POSITIVE_INFINITY;
    var north = Number.NEGATIVE_INFINITY;
    var east = Number.NEGATIVE_INFINITY;
    var minheight = Number.POSITIVE_INFINITY;
    var maxheight = Number.NEGATIVE_INFINITY;
    var boxParentTransform = Matrix4.IDENTITY;
    let minX = Infinity, minY = Infinity, minZ = Infinity;
    let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
    var inputDir = defaultValue(options.inputDir, './');
    var outputTileset = defaultValue(options.outputDir, path.join(inputDir, 'tileset.json'));
    var upAxis = 'Y';
    var originLon = 0;
    var originLat = 0;
    var originHeight = 0;
    if(typeof options.tilesetOptions === 'string') {
        var tilesetOptions = fsExtra.readJsonSync(options.tilesetOptions);
        upAxis = defaultValue(tilesetOptions.gltfUpAxis, 'Y');
        originLon = defaultValue(tilesetOptions.longitude, 0);
        originLat = defaultValue(tilesetOptions.latitude, 0);
        originHeight = defaultValue(tilesetOptions.transHeight, 0);
        var originPosition = Cesium.Cartesian3.fromRadians(originLon, originLat, originHeight);
        originLon = originPosition.x;
        originLat = originPosition.y;
        originHeight = originPosition.z;
    }
    var geometricError = 500;
    var children = [];
    var promises = [];
    var jsonFiles = [];
    var txList = [];
    var tyList = [];
    var tzList = [];
    inputDir = path.normalize(inputDir);
    outputTileset = path.normalize(outputTileset);
    var outputDir = path.dirname(outputTileset);

    getJsonFiles(inputDir, jsonFiles);
    jsonFiles.forEach(function(jsonFile) {
        var promise = fsExtra.readJson(jsonFile)
            .then(function(json) {
                if(!json.root) {return Promise.resolve();}
                var boundingVolume = json.root.boundingVolume;
                var transform = json.root.transform;
                var geometricError = json.geometricError;
                var refine = json.root.refine;

                if(defined(transform) && originLon !== 0 && originLat !== 0) {
                    // 计算父节点平移分量
                    const txChild = transform[12];
                    const tyChild = transform[13];
                    const tzChild = transform[14];
                    txList.push(originLon - txChild);
                    tyList.push(originLat - tyChild);
                    tzList.push(originHeight - tzChild);
                }
                
                if (defined(boundingVolume) && defined(geometricError)) {
                    // Use external tileset instand of b3dm.
                    var url = path.relative(outputDir, jsonFile);
                    url = url.replace(/\\/g, '/');

                    // Only support region for now.
                    if(boundingVolume.region) {
                        west = Math.min(west, boundingVolume.region[0]);
                        south = Math.min(south, boundingVolume.region[1]);
                        east = Math.max(east, boundingVolume.region[2]);
                        north = Math.max(north, boundingVolume.region[3]);
                        minheight = Math.min(minheight, boundingVolume.region[4]);
                        maxheight = Math.max(maxheight, boundingVolume.region[5]);
                    }else if(boundingVolume.box) {
                        isBox = true;
                        const box = boundingVolume.box;
                        boxParentTransform = transform;
                        const corners = computeBoxCorners(box);
                        corners.forEach(corner => {
                            minX = Math.min(minX, corner[0]);
                            minY = Math.min(minY, corner[1]);
                            minZ = Math.min(minZ, corner[2]);
                            maxX = Math.max(maxX, corner[0]);
                            maxY = Math.max(maxY, corner[1]);
                            maxZ = Math.max(maxZ, corner[2]);
                        });
                        // 去掉jsonFile里面的transform节点
                        json.root.transform = undefined;
                        fsExtra.writeJsonSync(jsonFile, json,{ spaces: 2 });
                    }

                    var child = {
                        'boundingVolume': boundingVolume,
                        'geometricError': geometricError,
                        'refine': refine,
                        'content': {
                            'url': url
                        }
                    };
                    children.push(child);
                }
            })
            .catch(function(err) {
                throw Error(err);
            });

        promises.push(promise);
    });

    return Promise.all(promises).then(function() {
        if (isBox) {
            // 计算中心点和半宽
            const centerX = (minX + maxX) / 2;
            const centerY = (minY + maxY) / 2;
            const centerZ = (minZ + maxZ) / 2;
            const halfWidth = (maxX - minX) / 2;
            const halfHeight = (maxY - minY) / 2;
            const halfDepth = (maxZ - minZ) / 2;
            const boundingVolume = {
                'box': [
                    centerX, centerY, centerZ,       // 中心点
                    halfWidth, 0, 0,                  // X轴
                    0, halfHeight, 0,                 // Y轴
                    0, 0, halfDepth                   // Z轴
                  ]
            };
            var tileset = {
                'asset': {
                    'version': '0.0',
                    'tilesetVersion': '1.0.0-obj2Tiles'
                },
                'geometricError': geometricError,
                'root': {
                    'transform' : boxParentTransform,
                    'boundingVolume': boundingVolume,
                    'refine': 'ADD',
                    'geometricError': geometricError,
                    'children': children
                }
            };
            return Promise.resolve({
                tileset: tileset,
                output: outputTileset
            });
        }
        // 非BOX方式
        // 计算平均值
        var transformArray = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
        if(txList.length!== 0){
            const avgTx = txList.reduce((sum, val) => sum + val, 0) / txList.length;
            const avgTy = tyList.reduce((sum, val) => sum + val, 0) / tyList.length;
            const avgTz = tzList.reduce((sum, val) => sum + val, 0) / tzList.length;
            transformArray = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, avgTx,
                avgTy, avgTz, 1];
        }
        var tileset = {
            'asset': {
                'version': '0.0',
                'tilesetVersion': '1.0.0-obj23dtiles',
				"gltfUpAxis": upAxis
            },
            'geometricError': geometricError,
            'root': {
                'transform' : transformArray,
                'boundingVolume': {
                    'region': [
                        west,
                        south,
                        east,
                        north,
                        minheight,
                        maxheight
                    ]
                },
                'refine': 'ADD',
                'geometricError': geometricError,
                'children': children
            }
        };
        return Promise.resolve({
            tileset: tileset,
            output: outputTileset
        });
    });
}

function getJsonFiles(dir, jsonFiles) {
    var files = fsExtra.readdirSync(dir);
    files.forEach(function (itm) {
        var fullpath = path.join(dir, itm);
        var stat = fsExtra.statSync(fullpath);
        if (stat.isDirectory()) {
            readFileList(fullpath, jsonFiles);
        }
    });
}

function readFileList(dir, jsonFiles) {
    var files = fsExtra.readdirSync(dir);
    files.forEach(function (itm) {
        var fullpath = path.join(dir, itm);
        var stat = fsExtra.statSync(fullpath);
        if (stat.isDirectory()) {
            readFileList(fullpath, jsonFiles);
        } else {
            var ext = path.extname(fullpath);
            if (ext === '.json'){
                jsonFiles.push(fullpath);
            }
        }
    });
}

// 计算向量加法
function vecAdd(a, b) {
    return [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
  }
  
  // 计算向量减法
  function vecSubtract(a, b) {
    return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
  }
  
  // 应用变换矩阵到点
  function transformPoint(point, transform) {
    const cartesian3 = Cartesian3.fromArray(point);
    return Matrix4.multiplyByPoint(transform, cartesian3, new Cartesian3());
  }
  
  // 从 box 参数计算 8 个角点
  function computeBoxCorners(box) {
    const center = [box[0], box[1], box[2]];
    const xAxis = [box[3], box[4], box[5]];
    const yAxis = [box[6], box[7], box[8]];
    const zAxis = [box[9], box[10], box[11]];
    
    const corners = [];
    for (let i = 0; i < 8; i++) {
      let corner = [...center];
      if (i & 1) corner = vecAdd(corner, xAxis);
      else corner = vecSubtract(corner, xAxis);
      
      if (i & 2) corner = vecAdd(corner, yAxis);
      else corner = vecSubtract(corner, yAxis);
      
      if (i & 4) corner = vecAdd(corner, zAxis);
      else corner = vecSubtract(corner, zAxis);
      
      corners.push(corner);
    }
    
    return corners;
  }
